<?php
namespace common\modules\user\frontend\controllers;

use common\commands\command\SendEmailCommand;
use common\modules\user\models\User;
use common\modules\user\models\UserAuthClient;
use common\modules\user\frontend\models\LoginForm;
use common\modules\user\frontend\models\PasswordResetRequestForm;
use common\modules\user\frontend\models\ResetPasswordForm;
use common\modules\user\frontend\models\SignupForm;
use Yii;
use yii\base\Exception;
use yii\base\InvalidParamException;
use yii\filters\AccessControl;
use yii\filters\VerbFilter;
use yii\helpers\ArrayHelper;
use yii\web\BadRequestHttpException;
use yii\web\Response;
use yii\widgets\ActiveForm;

class SignInController extends \yii\web\Controller
{
    /**
     * @inheritdoc
     */
    public function actions()
    {
        return [
            // Social auth
            'connect' => [
                'class' => 'yii\authclient\AuthAction',
                'successCallback' => [
                    $this,
                    'connectAuthCallback'
                ]
            ],
            'auth' => [
                'class' => 'yii\authclient\AuthAction',
                'successCallback' => [
                    $this,
                    'successAuthCallback'
                ]
            ]
        ];
    }

    /**
     * @inheritdoc
     */
    public function behaviors()
    {
        return [
            'access' => [
                'class' => AccessControl::className(),
                'rules' => [
                    [
                        'actions' => [
                            'signup',
                            'login',
                            'request-password-reset',
                            'reset-password',
                            'auth'
                        ],
                        'allow' => true,
                        'roles' => [
                            '?'
                        ]
                    ],
                    [
                        'actions' => [
                            'signup',
                            'login',
                            'request-password-reset',
                            'reset-password',
                            'auth'
                        ],
                        'allow' => false,
                        'roles' => [
                            '@'
                        ],
                        'denyCallback' => function ()
                        {
                            return Yii::$app->controller->redirect([
                                '/user/default/index'
                            ]);
                        }
                    ],
                    [
                        'actions' => [
                            'logout'
                        ],
                        'allow' => true,
                        'roles' => [
                            '@'
                        ]
                    ]
                ]
            ],
            'verbs' => [
                'class' => VerbFilter::className(),
                'actions' => [
                    'logout' => [
                        'post'
                    ]
                ]
            ]
        ];
    }

    public function actionLogin()
    {
        $model = new LoginForm();
        if (Yii::$app->request->isAjax) {
            $model->load($_POST);
            Yii::$app->response->format = Response::FORMAT_JSON;
            return ActiveForm::validate($model);
        }
        if ($model->load(Yii::$app->request->post()) && $model->login()) {
            return $this->goBack();
        } else {
            return $this->render('login', [
                'model' => $model
            ]);
        }
    }

    public function actionLogout()
    {
        Yii::$app->user->logout();
        return $this->goHome();
    }

    public function actionSignup()
    {
        $model = new SignupForm();
        if ($model->load(Yii::$app->request->post())) {
            $user = $model->signup();
            if ($user && Yii::$app->getUser()->login($user)) {
                return $this->goHome();
            }
        }
        
        return $this->render('signup', [
            'model' => $model
        ]);
    }

    public function actionRequestPasswordReset()
    {
        $model = new PasswordResetRequestForm();
        if ($model->load(Yii::$app->request->post()) && $model->validate()) {
            if ($model->sendEmail()) {
                Yii::$app->getSession()->setFlash('alert', [
                    'body' => Yii::t('frontend', 'Check your email for further instructions.'),
                    'options' => [
                        'class' => 'alert-success'
                    ]
                ]);
                
                return $this->goHome();
            } else {
                Yii::$app->getSession()->setFlash('alert', [
                    'body' => Yii::t('frontend', 'Sorry, we are unable to reset password for email provided.'),
                    'options' => [
                        'class' => 'alert-danger'
                    ]
                ]);
            }
        }
        
        return $this->render('requestPasswordResetToken', [
            'model' => $model
        ]);
    }

    public function actionResetPassword($token)
    {
        try {
            $model = new ResetPasswordForm($token);
        } catch (InvalidParamException $e) {
            throw new BadRequestHttpException($e->getMessage());
        }
        
        if ($model->load(Yii::$app->request->post()) && $model->validate() && $model->resetPassword()) {
            Yii::$app->getSession()->setFlash('alert', [
                'body' => Yii::t('frontend', 'New password was saved.'),
                'options' => [
                    'class' => 'alert-success'
                ]
            ]);
            return $this->goHome();
        }
        
        return $this->render('resetPassword', [
            'model' => $model
        ]);
    }

    /**
     * Connect social auth to the logged-in user
     *
     * @param \yii\authclient\BaseClient $client            
     * @return \yii\web\Response
     * @throws \yii\web\ForbiddenHttpException
     */
    public function connectAuthCallback($client)
    {
        // uncomment this to see which attributes you get back
        // echo "<pre>";print_r($client->getUserAttributes());echo "</pre>";exit;
        // check if user is not logged in. if so, do nothing
        if (Yii::$app->user->isGuest) {
            return;
        }
        // register a new user
        $userAuthClient = $this->initUserAuthClient($client);
        $userAuthClient->setUser(Yii::$app->user->id)->save();
    }

    /**
     *
     * @param $client \yii\authclient\BaseClient            
     * @return bool
     * @throws Exception
     */
    public function successAuthCallback($client)
    {
        // uncomment this to see which attributes you get back
        // echo "<pre>";print_r($client->getUserAttributes());echo "</pre>";exit;
        // check if user is already logged in. if so, do nothing
        if (! Yii::$app->user->isGuest) {
            return;
        }
        // attempt to log in as an existing user
        if ($this->attemptLogin($client)) {
            return;
        }
        // register a new user
        $userAuthClient = $this->initUserAuthClient($client);
        $this->registerAndLoginUser($client, $userAuthClient);
    }

    /**
     * Initialize a userAuthClient model based on $client data.
     * Note that we don't set
     * `user_id` yet because that can either be the currently logged in user OR a user
     * matched by email address
     *
     * @param \yii\authclient\BaseClient $client            
     * @return \common\models\UserAuthClient
     */
    protected function initUserAuthClient($client)
    {
        // build data. note that we don't set `user_id` yet
        $attributes = $client->getUserAttributes();
        $userAuthClient = new UserAuthClient();
        $userAuthClient->provider = $client->name;
        $userAuthClient->provider_user_id = (string) $attributes["id"];
        
        $userAuthClient->setProviderAttributes($attributes);
        return $userAuthClient;
    }

    /**
     * Attempt to log user in by checking if $userAuthClient already exists in the db,
     * or if a user already has the email address
     *
     * @param \yii\authclient\BaseClient $client            
     * @return bool
     */
    protected function attemptLogin($client)
    {
        // attempt to find userAuthClient in database by id and name
        $attributes = $client->getUserAttributes();
        $userAuthClient = UserAuthClient::findOne([
            "provider" => $client->name,
            "provider_user_id" => (string) $attributes["id"]
        ]);
        if ($userAuthClient) {
            $user = User::findOne($userAuthClient->user_id);
            // check if user is banned
            if ($user && $user->ban_time) {
                return false;
            }
            Yii::$app->user->login($user, Yii::$app->getModule("user")->loginDuration);
            return true;
        }
        $email = ArrayHelper::getValue($attributes, 'email');
        // attempt to find user by email
        // find user and create user provider for match
        $user = User::findOne([
            "email" => $email
        ]);
        if ($user) {
            $userAuthClient = $this->initUserAuthClient($client);
            $userAuthClient->setUser($user->id)->save();
            Yii::$app->user->login($user, Yii::$app->getModule("user")->loginDuration);
            return true;
        }
        return false;
    }

    /**
     * Register a new user using client attributes and then associate userAuthClient
     *
     * @param \yii\authclient\BaseClient $client            
     * @param \common\models\UserAuthClient $userAuthClient            
     */
    protected function registerAndLoginUser($client, $userAuthClient)
    {
        $attributes = $client->getUserAttributes();
        $username = ArrayHelper::getValue($attributes, 'login');
        
        // calculate and double check username (in case it is already taken)
        $fallbackUsername = "{$client->name}_{$userAuthClient->provider_user_id}";
        $username = $this->doubleCheckUsername($username, $fallbackUsername);
        
        // save new models
        $user = new User();
        $user->scenario = 'auth_create';
        $user->username = $username;
        $user->email = ArrayHelper::getValue($attributes, 'email');
        $password = Yii::$app->security->generateRandomString(8);
        $user->setPassword($password);
        $user->save(false);
        $userAuthClient->setUser($user->id)->save(false);
        // set profile info
        $user->afterSignup();
        $sentSuccess = Yii::$app->commandBus->handle(new SendEmailCommand([
            'view' => 'auth_welcome',
            'params' => [
                'user' => $user,
                'password' => $password
            ],
            'subject' => Yii::t('frontend', '{app-name} | Your login information', [
                'app-name' => Yii::$app->name
            ]),
            'to' => $user->email
        ]));
        if ($sentSuccess) {
            Yii::$app->session->setFlash('alert', [
            'options' => [
            'class' => 'alert-success'
                ],
                'body' => Yii::t('frontend', 'Welcome to {app-name}. Email with your login information was sent to your email.', [
                'app-name' => Yii::$app->name
                ])
                ]);
        }

        // log user in
        Yii::$app->user->login($user, Yii::$app->getModule("user")->loginDuration);
    }

    /**
     * Double checks username to ensure that it isn't already taken.
     * If so,
     * revert to fallback
     *
     * @param \common\models\User $user            
     * @param string $fallbackUsername            
     * @return string username
     */
    protected function doubleCheckUsername($username, $fallbackUsername)
    {
        // replace periods with underscore to match user rules
        $username = str_replace(".", "_", $username);
        // check unique username
        $userCheck = User::findOne([
            "username" => $username
        ]);
        if ($userCheck) {
            $username = $fallbackUsername;
        }
        return $username;
    }
}